/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-03
 * V4.0
 */
package com.jphenix.driver.internationalization;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.servlet.filter.FilterExplorer;
import com.jphenix.share.lang.SString;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.servlet.IBytesFilter;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequest;
import com.jphenix.standard.servlet.IResponse;

import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * 国际化处理过滤器，主要处理静态页面中的国际化信息
 * 国际化信息格式： {*[CHS:简体中文信息][CHT:繁体中文信息][EN:英文信息]*}
 * 
 * 2019-06-15 按照IFilter增加了过滤器初始化方法
 * 2019-08-19 修改了字节流处理方法，提高效率
 * 
 * @author 刘虻
 * 2013-4-6 下午6:50:24
 */
@ClassInfo({"2019-08-19 17:30","国际化处理过滤器"})
public class LanguageFilter extends ABase implements IFilter,IBytesFilter {

	public static final int BUFFER_SIZE = 128;  //段值
	
	protected FilterConfig config       = null; //过滤器配置信息处理类
	protected String       languageCode = null; //语言代码
	
	/**
	 * 构造函数
	 * @author 刘虻
	 */
	public LanguageFilter() {
		super();
	}

	/**
	 * 覆盖方法
	 * 刘虻
	 * 2013-4-6 下午6:50:24
	 */
	@Override
    public int getIndex() {
		return 10; //优先级比较高 最高为0
	}
	
	/**
	 * 执行初始化
	 * @param fe         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(FilterExplorer fe, FilterConfig config) throws Exception {
		this.config = config;
	}
	
	/**
	 * 覆盖方法
	 * 刘虻
	 * 2013-4-6 下午6:50:25
	 * @deprecated
	 */
    @Override
    public boolean doFilter(IRequest req, IResponse resp) throws Exception {
		//动作路径
		String uri  = req.getServletPath();
		//文件路径
		String filename = SFilesUtil.getAllFilePath(uri,req.getWebBasePath());
		File file = new File(filename); //构建要解析的文件
		if (file.exists()) {
			if (!file.isDirectory()) {
				serveFile(req, resp, canonicalizePath(uri), file);
			} else {
				log.log("Language Error->Not found the file:["+filename+"]");
				resp.sendError(HttpServletResponse.SC_NOT_FOUND,req);
			}
		} else {
			log.log("Language Error->Not found the file:["+filename+"]");
			resp.sendError(HttpServletResponse.SC_NOT_FOUND,req);
		}
        /*
         * 标记该动作已经被处理完
         * 因为操作过读入流，该过滤器读取完之后，
         * 就不能被其他过滤器读取了。
         */
		return true;
	}
	
	
	/**
	 * 处理目标文件相对路径
	 * 刘虻
	 * 2012-7-27 上午9:01:40
	 * @param path 页面请求中的文件路径信息
	 * @return 处理后的文件相对路径
	 */
	protected String canonicalizePath(String path) {
		if (path == null || path.length() == 0) {
            return path;
        }
		List<String> pathElems = new ArrayList<String>(6);
		char[] pa = path.toCharArray();
		int n = pa.length;
		int s = -1;
		int lev = 0;
		for (int i = 0; i < n; i++) {
			if (s < 0) {
				if (pa[i] != '/' && pa[i] != '\\') {
                    s = i;
                }
			} else {
				boolean f = false;
				if (pa[i] == '?') {
                    f = true;
                }
				if (pa[i] == '/' || pa[i] == '\\' || f) {
					String el = new String(pa, s, i - s);
					if ("..".equals(el)) {
						if (pathElems.size() > 0) {
                            pathElems.remove(pathElems.size() - 1);
                        } else {
                            lev--;
                        }
						// else exception ?
					} else if (".".equals(el) == false) {
                        if (lev >= 0) {
                            pathElems.add(el);
                        } else {
                            lev++;
                        }
                    }
					if (f) {
						s = i;
						break;
					}
					s = -1;
				}
			}
		}
		if (s > 0) {
			String el = new String(pa, s, n - s);
			if ("..".equals(el)) {
				if (pathElems.size() > 0) {
                    pathElems.remove(pathElems.size() - 1);
                }
				// else exception ?
			} else if (".".equals(el) == false) {
                if (lev >= 0) {
                    pathElems.add(el);
                }
            }
		} else {
            pathElems.add("");
        }
		if (pathElems.size() == 0) {
            return "";
        }
		StringBuffer result = new StringBuffer(n);
		result.append(pathElems.get(0));
		n = pathElems.size();
		for (int i = 1; i < n; i++) {
			result.append('/').append(pathElems.get(i));
		}
		// System.err.println("Before "+path+" after "+result);
		return result.toString();
	}
	
	/**
	 * 处理文件内容
	 * 刘虻
	 * 2013-4-6 下午10:03:25
	 * @param file 指定文件
	 * @return 处理后的文件内容字节数组
	 * @throws Exception 异常
	 */
	protected byte[] fixContent(File file) throws Exception {
		//构建返回值
		ByteArrayOutputStream reBos = new ByteArrayOutputStream();
		FileInputStream in = new FileInputStream(file); //文件读入流
		/*
		 * 开头标识
		 * 0没有读到关键字
		 * 1读到关键字的第一个字节 {
		 * 2读到完整的关键字,准备解析语言 {*
		 * 3读取到一个语言段的开始[
		 * 4读取到语言代码结束点： 准备开始读取语言内容
		 * 5读取到语言信息段结束标识的第一个字节 *
		 */
		int bStep = 0;
		try {
			byte[]                buffer             = new byte[BUFFER_SIZE]; //缓存大小
			int                   rCount;                     //读取的字节数量
			ByteArrayOutputStream codeBos            = null;  //读取到的语言编码
			ByteArrayOutputStream infoBos            = null;  //读取到指定语言字节数组
			ByteArrayOutputStream firstInfoBos       = null;  //读取到的首个语言内容，作为无指定语言代码时默认输出
			boolean               firstInfo          = false; //是否为首个语言内容
			boolean               found              = false; //是否发现要输出的语言
			boolean               firstInfoCompleted = false; //首个语言内容是否读取完毕
			boolean               foundCompleted     = false; //指定语言内容是否读取完毕
			while((rCount=in.read(buffer))!=-1) {
				for(int i=0;i<rCount;i++) {
					if(bStep==0) {
						//获取处理信息节点关键字的第一个字节
						if(buffer[i]=='{') {
							bStep=1;
						}else {
							reBos.write(buffer[i]);
						}
					}else if(bStep==1) {
						//获取处理信息节点的最后一个字节（第二个字节）
						if(buffer[i]=='*') {
							bStep = 2;
							codeBos = new ByteArrayOutputStream();
							infoBos = new ByteArrayOutputStream();
							firstInfoBos = new ByteArrayOutputStream();
							firstInfo = true;
							found = false;
							firstInfoCompleted = false;
							foundCompleted = false;
						}else {
							//头一个{不是关键字
							bStep = 0;
							reBos.write('{');
							reBos.write(buffer[i]);
						}
					}else if(bStep==2) {
						//处理语言代码信息
						
						//获取语言代码段开始[
						if(buffer[i]=='[') {
							bStep = 3;
						}else if(buffer[i]=='*') {
							//获取结束标识开始
							bStep = 5;
						}else {
							//忽略不符合规范的代码
						}
					}else if(bStep==3 && codeBos!=null) {
						if(buffer[i]==':') {
							//语言代码接收完毕，准备接收语言内容
							bStep  = 4;
							//判断是否为指定语言
							if(codeBos.toString().equals(languageCode)) {
								found = true;
							}
							codeBos = new ByteArrayOutputStream();
						}else if(buffer[i]==']') {
							//语言内容接收完毕
							bStep = 2;
						}else {
							//记录
							codeBos.write(buffer[i]);
						}
					}else if(bStep==4) {
						//开始读取语言内容
						if(buffer[i]==']') {
							//读取语言段结束
							bStep = 2; //返回准备读取下一个语言段
							firstInfoCompleted = true; //首个语言内容已经读取完毕
							if(found) {
								foundCompleted = true; //如果指定语言内容已经找到，标记已经读取完毕
							}
						}else {
							if(found && !foundCompleted && infoBos!=null) {
								//找到指定的语言内容
								infoBos.write(buffer[i]);
							}else if(firstInfo && !firstInfoCompleted && firstInfoBos!=null) {
								//首个语言段内容
								firstInfoBos.write(buffer[i]);
							}
						}
					}else if(bStep==5) {
						if(buffer[i]=='}') {
							//读取到结束标识完整关键字
							bStep = 0; //准备读取下一个语言节点
							if(found && infoBos!=null) {
								reBos.write(infoBos.toByteArray());
							}else if(firstInfo && firstInfoBos!=null) {
								reBos.write(firstInfoBos.toByteArray());
							}
						}else {
							bStep = 2; //错误的关键字，回到处理语言段
						}
					}
				}
			}
		}finally {
			try {
				in.close();
			}catch(Exception e2) {}
		}
		return reBos.toByteArray();
	}
	
	
	/**
	 * 输出指定文件内容到页面中
	 * 刘虻
	 * 2012-7-27 上午9:09:36
	 * @param req						页面请求
	 * @param res						页面反馈
	 * @param path					文件路径
	 * @param file						文件对象
	 * @throws IOException		异常
	 */
	protected void serveFile(
			 HttpServletRequest req
			,HttpServletResponse res
			,String path
			,File file) throws IOException {
		if (!file.canRead()) {
			if(res instanceof IResponse && req instanceof IRequest) {
				((IResponse)res).sendError(HttpServletResponse.SC_FORBIDDEN,(IRequest)req);
			}else {
				res.sendError(HttpServletResponse.SC_FORBIDDEN);
			}
			return;
		} else
			// by Niel Markwick
        {
            try {
                file.getCanonicalPath();
            } catch (Exception e) {
                if(res instanceof IResponse && req instanceof IRequest) {
                    ((IResponse)res).sendError(HttpServletResponse.SC_FORBIDDEN,"Forbidden, exception:" + e,(IRequest)req);
                }else {
                    res.sendError(HttpServletResponse.SC_FORBIDDEN,"Forbidden, exception:" + e);
                }
                return;
            }
        }
		// Handle If-Modified-Since.
		res.setStatus(HttpServletResponse.SC_OK);
		//设置内容类型
		res.setContentType(config.getServletContext().getMimeType(file.getName()));
		
		//先从会话中获取语言代码
		String languageCode = 
				SString.valueOf(req.getSession().getAttribute("locale"));
		if(languageCode.length()<1) {
			languageCode = req.getLocale().toString().toUpperCase();
		}
		//转译后的代码
		String oCode = 
				SString.valueOf(
						getLanguageManager()
							.getLanguageCodeMap().get(languageCode));
		if(oCode.length()>0) {
			languageCode = oCode;
		}
		//获取处理后的文件内容
		byte[] contentBytes;
		try {
			contentBytes= fixContent(file); 
		}catch(Exception e) {
			e.printStackTrace();
			if(res instanceof IResponse && req instanceof IRequest) {
				((IResponse)res).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,(IRequest)req);
			}else {
				res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			}
			return;
		}
		if (contentBytes.length < Integer.MAX_VALUE) {
			res.setContentLength(contentBytes.length);
		}else {
			res.setHeader("Content-Length", Long.toString(contentBytes.length));
		}
		res.setDateHeader("Last-modified", file.lastModified());
		try {
			//输出内容
			res.getOutputStream().write(contentBytes);
		}catch(Exception e) {}
	}

    /**
     * 执行过滤
     * @param bytes 过滤前的字节数组
     * @param req 页面请求对象 
     * @param resp 页面反馈对象
     * @return 过滤后的字节数组
     * 2014年6月30日
     * @author 马宝刚
     */
    @Override
    public byte[] doBytesFilter(byte[] bytes,IRequest req,IResponse resp) {
        //先从会话中获取语言代码
        String languageCode = 
                SString.valueOf(req.getSession().getAttribute("locale"));
        if(languageCode.length()<1) {
            languageCode = req.getLocale().toString().toUpperCase();
        }
        //转译后的代码
        String oCode = 
                SString.valueOf(
                        getLanguageManager()
                            .getLanguageCodeMap().get(languageCode));
        if(oCode.length()>0) {
            languageCode = oCode;
        }

        //构建返回值
        ByteArrayOutputStream reBos = new ByteArrayOutputStream();
        /*
         * 开头标识
         * 0没有读到关键字
         * 1读到关键字的第一个字节 {
         * 2读到完整的关键字,准备解析语言 {*
         * 3读取到一个语言段的开始[
         * 4读取到语言代码结束点： 准备开始读取语言内容
         * 5读取到语言信息段结束标识的第一个字节 *
         */
        int bStep = 0;
        try {
            ByteArrayOutputStream codeBos = null; //读取到的语言编码
            ByteArrayOutputStream infoBos = null;  //读取到指定语言字节数组
            ByteArrayOutputStream firstInfoBos = null; //读取到的首个语言内容，作为无指定语言代码时默认输出
            boolean firstInfo = false; //是否为首个语言内容
            boolean found = false; //是否发现要输出的语言
            boolean firstInfoCompleted = false; //首个语言内容是否读取完毕
            boolean foundCompleted = false; //指定语言内容是否读取完毕
            for(byte ele:bytes) {
                if(bStep==0) {
                    //获取处理信息节点关键字的第一个字节
                    if(ele=='{') {
                        bStep=1;
                    }else {
                        reBos.write(ele);
                    }
                }else if(bStep==1) {
                    //获取处理信息节点的最后一个字节（第二个字节）
                    if(ele=='*') {
                        bStep = 2;
                        codeBos = new ByteArrayOutputStream();
                        infoBos = new ByteArrayOutputStream();
                        firstInfoBos = new ByteArrayOutputStream();
                        firstInfo = true;
                        found = false;
                        firstInfoCompleted = false;
                        foundCompleted = false;
                    }else {
                        //头一个{不是关键字
                        bStep = 0;
                        reBos.write('{');
                        reBos.write(ele);
                    }
                }else if(bStep==2) {
                    //处理语言代码信息
                    
                    //获取语言代码段开始[
                    if(ele=='[') {
                        bStep = 3;
                    }else if(ele=='*') {
                        //获取结束标识开始
                        bStep = 5;
                    }else {
                        //忽略不符合规范的代码
                    }
                }else if(bStep==3 && codeBos!=null) {
                    if(ele==':') {
                        //语言代码接收完毕，准备接收语言内容
                        bStep  = 4;
                        //判断是否为指定语言
                        if(codeBos.toString().equals(languageCode)) {
                            found = true;
                        }
                        codeBos = new ByteArrayOutputStream();
                    }else if(ele==']') {
                        //语言内容接收完毕
                        bStep = 2;
                    }else {
                        //记录
                        codeBos.write(ele);
                    }
                }else if(bStep==4) {
                    //开始读取语言内容
                    if(ele==']') {
                        //读取语言段结束
                        bStep = 2; //返回准备读取下一个语言段
                        firstInfoCompleted = true; //首个语言内容已经读取完毕
                        if(found) {
                            foundCompleted = true; //如果指定语言内容已经找到，标记已经读取完毕
                        }
                    }else {
                        if(found && !foundCompleted && infoBos!=null) {
                            //找到指定的语言内容
                            infoBos.write(ele);
                        }else if(firstInfo && !firstInfoCompleted && firstInfoBos!=null) {
                            //首个语言段内容
                            firstInfoBos.write(ele);
                        }
                    }
                }else if(bStep==5) {
                    if(ele=='}') {
                        //读取到结束标识完整关键字
                        bStep = 0; //准备读取下一个语言节点
                        if(found && infoBos!=null) {
                            reBos.write(infoBos.toByteArray());
                        }else if(firstInfo && firstInfoBos!=null) {
                            reBos.write(firstInfoBos.toByteArray());
                        }
                    }else {
                        bStep = 2; //错误的关键字，回到处理语言段
                    }
                }
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
        return reBos.toByteArray();
    }
    
    
    /**
     * 获取需要过滤的动作路径扩展名
     * 
     * 多个扩展名用逗号分割
     * 多个扩展名用半角逗号分割
     * 扩展名前不用加半角聚号（.)
     * 如果返回空，或者空字符串，说明需要过滤全部动作路径（不建议这么做）
     * 如果需要过滤无扩展名的动作，用半角减号（-）标记
     * 
     * @return
     * 2014年9月12日
     * @author 马宝刚
     */
    @Override
    public String getFilterActionExtName() {
        return "html,js,css";
    }


    /**
     * 读取指定文档内容并做内容处理
     * @param filePath     文档全路径
     * @param req          页面请求
     * @param resp         页面反馈
     * @return             处理后的文件内容
     * @throws Exception   异常
     * 2017年9月11日
     * @author MBG
     */
	@Override
	public byte[] doFileFilter(String filePath, IRequest req, IResponse resp) throws Exception {
    	//目标文件
    	File file = new File(filePath);
    	if(!file.exists()) {
    		return null;
    	}
        return doBytesFilter(FileCopyTools.copyToByteArray(file),req,resp);
	}
}
